package org.zjvis.datascience.web.controller;

import cn.hutool.core.util.RandomUtil;
import cn.weiguangfu.swagger2.plus.annotation.ApiPlus;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.zjvis.datascience.common.config.ConfigManager;
import org.zjvis.datascience.common.dto.PluginDTO;
import org.zjvis.datascience.common.dto.PluginInstanceDTO;
import org.zjvis.datascience.common.model.ApiResult;
import org.zjvis.datascience.common.model.ApiResultCode;
import org.zjvis.datascience.common.util.FileUtil;
import org.zjvis.datascience.common.util.JwtUtil;
import org.zjvis.datascience.common.util.Page;
import org.zjvis.datascience.common.vo.PluginInstanceVO;
import org.zjvis.datascience.common.vo.PluginQueryVO;
import org.zjvis.datascience.common.vo.PluginVO;
import org.zjvis.datascience.service.PluginInstanceService;
import org.zjvis.datascience.service.PluginService;
import org.zjvis.datascience.service.mapper.SqlMapper;

import javax.validation.constraints.NotNull;
import java.io.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;

@ApiPlus(value = true)
@RequestMapping("/plugin")
@RestController
@Api(tags = "plugin", description = "插件接口")
@Validated
public class PluginController {
    private static final Logger logger = LoggerFactory.getLogger(PluginController.class);

    private static final Integer PLUGIN_STATUS_UNINSTALLED = 0;
    private static final Integer PLUGIN_STATUS_INSTALLED = 1;
    private static final Integer PLUGIN_STATUS_INSTALLING = 2;
    private static final Integer PLUGIN_STATUS_UNINSTALLING = 3;

    @Autowired
    private PluginService pluginService;

    @Autowired
    private ConfigManager configManager;

    @Autowired
    private PluginInstanceService pluginInstanceService;

    @Autowired
    private SqlMapper sqlMapper;

    @PostMapping("/register")
    @ResponseBody
    @ApiOperation(value = "注册插件", notes = "注册插件")
    @Transactional
    public ApiResult<Boolean> register(@RequestBody PluginVO vo) {
        PluginDTO plugin = vo.toPlugin();
        try {
            pluginService.save(plugin);
        } catch (DuplicateKeyException e) {
            PluginDTO oldPlugin = pluginService.queryByName(vo.getName());
            oldPlugin.setUrl(plugin.getUrl());
            oldPlugin.setVersion(plugin.getVersion());
            oldPlugin.setIconEncode(plugin.getIconEncode());
            oldPlugin.setDataJson(plugin.getDataJson());
            oldPlugin.setDescription(plugin.getDescription());
            pluginService.update(oldPlugin);
        }
        return ApiResult.valueOf(true);
    }

    @PostMapping("/enable")
    @ResponseBody
    @ApiOperation(value = "启用插件", notes = "启用插件")
    @Transactional
    public ApiResult<Boolean> enable(@RequestBody PluginVO vo) {
        PluginDTO plugin = pluginService.queryByToken(vo.getToken());
        logger.info("plugin enable: {}", plugin);
        if (plugin == null) {
            return ApiResult.valueOf(ApiResultCode.PLUGIN_NOT_FOUND, false);
        }
        return ApiResult.valueOf(true);
    }

    @GetMapping("/queryConfig")
    @ResponseBody
    @ApiOperation(value = "查询配置信息", notes = "查询配置信息")
    @Transactional
    public ApiResult<String> queryConfig(@RequestParam(value="key")String key, @RequestParam(value="token")String token) {
        if (!configManager.checkToken(token)) {
            return ApiResult.valueOf(ApiResultCode.TOKEN_UNAUTHORIZED);
        }
        Map<String, String> config = configManager.getConfig();
        String val = config.get(key);
        return ApiResult.valueOf(val);
    }

    @PostMapping("/install")
    @ResponseBody
    @ApiOperation(value = "安装插件", notes = "安装插件")
    @Transactional
    public ApiResult<Long> install(@RequestBody PluginVO vo) {
        if (vo == null || vo.getId() == null) {
            return ApiResult.valueOf(ApiResultCode.PARAM_ERROR);
        }
        PluginDTO plugin = pluginService.queryById(vo.getId());
        if (plugin == null) {
            return ApiResult.valueOf(ApiResultCode.PLUGIN_NOT_FOUND);
        }
        if (!plugin.getStatus().equals(PLUGIN_STATUS_UNINSTALLED)) {
            return ApiResult.valueOf(ApiResultCode.PLUGIN_ALREADY_INSTALLED);
        }
        plugin.setStatus(PLUGIN_STATUS_INSTALLING);
        pluginService.update(plugin);

        PluginInstanceDTO instance = new PluginInstanceDTO();
        instance.setPluginId(vo.getId());
        instance.setUserId(JwtUtil.getCurrentUserId());
        JSONObject data = new JSONObject();
        data.put("type", "install");
        instance.setDataJson(data.toJSONString());
        Long instanceId = pluginInstanceService.save(instance);
        JSONObject pluginData = JSONObject.parseObject(plugin.getDataJson());
        String token = plugin.getToken();
        new Thread(() -> {
            instance.setStatus("running");
            pluginInstanceService.update(instance);
            long t1 = System.currentTimeMillis();
            Process process = null;
            try {
                String git = pluginData.getString("git");
                String branch = pluginData.getString("branch");
                String workdir = pluginData.getString("workdir");
                logger.info("installPlugin: git: {}  branch:  {}  workdir:  {}", git, branch, workdir);
                String cmd = String.format("/app/bin/pluginInstall.sh %s %s %s %s", branch, git, workdir, token);
                logger.info("installPlugin: cmd: {}", cmd);
                process = Runtime.getRuntime().exec(cmd);
                int status = process.waitFor();
                logger.info("plugin install process exitValue: " + status);
                long t2 = System.currentTimeMillis();
                String instanceStatus = status == 0 ? "success" : "fail";
                Integer pluginStatus = status == 0 ? PLUGIN_STATUS_INSTALLED : PLUGIN_STATUS_UNINSTALLED;
                instance.setStatus(instanceStatus);
                instance.setDuringTime(t2-t1);
                pluginInstanceService.update(instance);
                PluginDTO _plugin = pluginService.queryById(vo.getId());
                logger.info("plugin info: {}", _plugin);
                _plugin.setStatus(pluginStatus);
                pluginService.update(_plugin);
                logger.info("set plugin status: " + _plugin.getStatus());
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
                if (process != null && !process.isAlive()) {
                    logger.info("plugin install exception exitValue: " + process.exitValue());
                }
                long t2 = System.currentTimeMillis();
                instance.setStatus("fail");
                instance.setDuringTime(t2-t1);
                pluginInstanceService.update(instance);
                PluginDTO _plugin = pluginService.queryById(vo.getId());
                logger.info("plugin info: {}", _plugin);
                _plugin.setStatus(PLUGIN_STATUS_UNINSTALLED);
                pluginService.update(_plugin);
                logger.info("set plugin status: " + _plugin.getStatus());
            }
        }).start();
        return ApiResult.valueOf(instanceId);
    }

    @PostMapping("/uninstall")
    @ResponseBody
    @ApiOperation(value = "卸载插件", notes = "卸载插件")
    @Transactional
    public ApiResult<Long> uninstall(@RequestBody PluginVO vo) {
        if (vo == null || vo.getId() == null) {
            return ApiResult.valueOf(ApiResultCode.PARAM_ERROR);
        }
        PluginDTO plugin = pluginService.queryById(vo.getId());
        if (plugin == null) {
            return ApiResult.valueOf(ApiResultCode.PLUGIN_NOT_FOUND);
        }
        if (!plugin.getStatus().equals(PLUGIN_STATUS_INSTALLED)) {
            return ApiResult.valueOf(ApiResultCode.PLUGIN_ALREADY_UNINSTALLED);
        }
        plugin.setStatus(PLUGIN_STATUS_UNINSTALLING);
        pluginService.update(plugin);

        PluginInstanceDTO instance = new PluginInstanceDTO();
        instance.setPluginId(vo.getId());
        instance.setUserId(JwtUtil.getCurrentUserId());
        JSONObject data = new JSONObject();
        data.put("type", "uninstall");
        instance.setDataJson(data.toJSONString());
        Long instanceId = pluginInstanceService.save(instance);
        JSONObject pluginData = JSONObject.parseObject(plugin.getDataJson());
        String token = plugin.getToken();
        new Thread(() -> {
            instance.setStatus("running");
            pluginInstanceService.update(instance);
            long t1 = System.currentTimeMillis();
            Process process = null;
            try {
                String git = pluginData.getString("git");
                String branch = pluginData.getString("branch");
                String workdir = pluginData.getString("workdir");
                logger.info("uninstallPlugin: git: {}  branch:  {}  workdir:  {}", git, branch, workdir);
                String cmd = String.format("/app/bin/pluginUninstall.sh %s %s %s %s", branch, git, workdir, token);
                logger.info("uninstallPlugin: cmd: {}", cmd);
                process = Runtime.getRuntime().exec(cmd);
                int status = process.waitFor();
                logger.info("plugin uninstall process exitValue: " + status);
                long t2 = System.currentTimeMillis();
                String instanceStatus = status == 0 ? "success" : "fail";
                Integer pluginStatus = status == 0 ? PLUGIN_STATUS_UNINSTALLED : PLUGIN_STATUS_INSTALLED;
                instance.setStatus(instanceStatus);
                instance.setDuringTime(t2-t1);
                pluginInstanceService.update(instance);
                PluginDTO _plugin = pluginService.queryById(vo.getId());
                logger.info("plugin info: {}", _plugin);
                _plugin.setStatus(pluginStatus);
                pluginService.update(_plugin);
                logger.info("set plugin status: " + _plugin.getStatus());
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
                if (process != null && !process.isAlive()) {
                    logger.info("plugin uninstall exception exitValue: " + process.exitValue());
                }
                long t2 = System.currentTimeMillis();
                instance.setStatus("fail");
                instance.setDuringTime(t2-t1);
                pluginInstanceService.update(instance);
                PluginDTO _plugin = pluginService.queryById(vo.getId());
                logger.info("plugin info: {}", _plugin);
                _plugin.setStatus(PLUGIN_STATUS_INSTALLED);
                pluginService.update(_plugin);
                logger.info("set plugin status: " + _plugin.getStatus());
            }
        }).start();
        return ApiResult.valueOf(instanceId);
    }

    @PostMapping("/queryStatus")
    @ResponseBody
    @ApiOperation(value = "安装任务轮询", notes = "安装任务轮询")
    @Transactional
    public ApiResult<List<PluginInstanceVO>> queryStatus(@RequestBody JSONObject params) {
        List<Long> ids = params.getJSONArray("id").toJavaList(Long.class);
        List<PluginInstanceVO> ret = new ArrayList<>();
        for (Long id : ids) {
            PluginInstanceDTO instance = pluginInstanceService.queryById(id);
            //读log
            String filePath = "";
            String type = JSONObject.parseObject(instance.getDataJson()).getString("type");
            if (type != null && type.equals("install")) {
                filePath = "/tmp/pluginInstall.log";
            } else if (type != null && type.equals("uninstall")) {
                filePath = "/tmp/pluginUninstall.log";
            }
            String log = "";
            if (StringUtils.isNotEmpty(filePath)) {
                try {
                    InputStream in = new FileInputStream(filePath);
                    log = FileUtil.inputStream2String(in, true);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            instance.setLogInfo(log);
            ret.add(instance.view());
        }
        return ApiResult.valueOf(ret);
    }

    @PostMapping("/queryEnablePlugin")
    @ResponseBody
    @ApiOperation(value = "查询已安装插件", notes = "查询已安装插件")
    @Transactional
    public ApiResult<List<PluginDTO>> queryEnablePlugin() {
        return ApiResult.valueOf(pluginService.queryByStatus());
    }

    @PostMapping("/queryAllPlugin")
    @ResponseBody
    @ApiOperation(value = "查询所有插件", notes = "查询所有插件")
    @Transactional
    public ApiResult<Page<PluginVO>> queryAllPlugin(@RequestBody PluginQueryVO vo) {
        Page<PluginVO> page = pluginService.queryByCondition(vo);
        List<PluginVO> views = page.getData();
        for (PluginVO view : views) {
            if (view.getStatus().equals(PLUGIN_STATUS_INSTALLING) || view.getStatus().equals(PLUGIN_STATUS_UNINSTALLING)) {
                view.setInstanceId(pluginInstanceService.queryLatestIdByPluginId(view.getId()));
            }
        }
        return ApiResult.valueOf(page);
    }

    @PostMapping("/createDatabase")
    @ResponseBody
    @ApiOperation(value = "创建数据库", notes = "创建数据库")
    @Transactional
    public ApiResult<JSONObject> createDatabase(@RequestParam(value = "database") @NotNull String database,
                                                @RequestParam(value = "token") @NotNull String token) {
        PluginDTO plugin = pluginService.queryByToken(token);
        logger.info("database plugin, {}", plugin);
        if (plugin == null) {
            return ApiResult.valueOf(ApiResultCode.TOKEN_UNAUTHORIZED);
        }
        JSONObject data = JSONObject.parseObject(plugin.getDataJson());
        if (data.getString("database") != null && !data.getString("database").equals(database)) {
            return ApiResult.valueOf(ApiResultCode.PLUGIN_DATABASE_ALREADY_CREATE);
        }
        if (data.getString("database") != null && data.getString("database").equals(database)) {
            JSONObject ret = new JSONObject();
            ret.put("username", data.getString("username"));
            ret.put("password", data.getString("password"));
            logger.info("database already exist,  return {}", ret);
            return ApiResult.valueOf(ret);
        }
        String username = "plugin_" + RandomUtil.randomString(4);
        String password = Base64.getEncoder().encodeToString(RandomUtil.randomBytes(10));
        sqlMapper.createDatabase(database);
        sqlMapper.createUser(username, password);
        sqlMapper.granDatabaseToUser(database, username);
        logger.info("grant on database: {},  user: {},  password:  {}", database, username, password);
        data.put("database", database);
        data.put("username", username);
        data.put("password", password);
        plugin.setDataJson(data.toJSONString());
        pluginService.update(plugin);
        JSONObject ret = new JSONObject();
        ret.put("username", username);
        ret.put("password", password);
        return ApiResult.valueOf(ret);
    }

    @PostMapping("/deleteDatabase")
    @ResponseBody
    @ApiOperation(value = "删除数据库", notes = "删除数据库")
    @Transactional
    public ApiResult<Void> deleteDatabase(@RequestParam(value = "database") @NotNull String database,
                                          @RequestParam(value = "token") @NotNull String token) {
        PluginDTO plugin = pluginService.queryByToken(token);
        logger.info("database plugin, {}", plugin);
        if (plugin == null) {
            return ApiResult.valueOf(ApiResultCode.TOKEN_UNAUTHORIZED);
        }
        JSONObject data = JSONObject.parseObject(plugin.getDataJson());
        if (data.getString("database") == null) {
            return ApiResult.valueOf(ApiResultCode.SUCCESS);
        }
        if (!data.getString("database").equals(database)) {
            return ApiResult.valueOf(ApiResultCode.PLUGIN_DATABASE_UNAUTHORIZED);
        }
        String username = data.getString("username");
        if (username != null) {
            sqlMapper.deleteUser(username);
        }
        sqlMapper.deleteDatabase(database);
        data.remove("database");
        data.remove("username");
        data.remove("password");
        plugin.setDataJson(data.toJSONString());
        pluginService.update(plugin);
        logger.info("delete database: {},  delete user: {}", database, username);
        return ApiResult.valueOf(ApiResultCode.SUCCESS);
    }

    public static void main(String[] args) {
        String token = RandomUtil.randomString(40);
        System.out.println(token);
    }
}
